/*
 * vga.c
 *
 * Creado: 27/11/12
 * Autor: Pablo Manuelli
 * 
 * Funciones de VGA
 *  
 */
 
#include <vga.h>
#include <i386.h>
#include <string.h>
#include <stdarg.h>
#include <stdlib.h>

#define SCREEN_CURR_LINE_BEGIN_ADDR(pos) ((uint_32) ((((pos - SCREEN_ADDR_START) / SCREEN_LINE_SIZE) * SCREEN_LINE_SIZE) + SCREEN_ADDR_START))
#define SCREEN_NEXT_LINE_BEGIN_ADDR(pos) ((uint_32) (SCREEN_CURR_LINE_BEGIN_ADDR(pos) + SCREEN_LINE_SIZE))
#define SCREEN_RELATIVE_POSITION(pos) 	 ((uint_16) ((pos - SCREEN_ADDR_START) / SCREEN_CHAR_SIZE))
#define SCREEN_ADDR_RC(row, colum)		 ((uint_32) SCREEN_ADDR_START + colum * SCREEN_CHAR_SIZE + row * SCREEN_LINE_SIZE)

#define SCREEN_CONFIG_BYTE(chr, scrn)	 ((uint_8) (chr | (scrn << 4)))

uint_32 vga_curr_pos;			// Posicion actual del cursor

uint_8 vga_curr_char_color;		// Color actual de la fuente
uint_8 vga_curr_scrn_color;		// Color actual del fondo

/* 
 * vga_init - Inicializa las estructuras del controlador de VGA
 *
 */

void vga_init()
{
	vga_curr_pos = SCREEN_ADDR_START;
	vga_curr_char_color = WHITE;
	vga_curr_scrn_color = BLACK;
	
	// Limpio la pantalla
	
	vga_clrscr();
	vga_refresh_screen();
}

/* 
 * vga_set_cursor - Posiciona el cursor en la fila @row y columna @column
 * 
 */

void vga_set_cursor(uint_32 row, uint_32 column)
{
	vga_curr_pos = SCREEN_ADDR_RC(row, column);
	vga_refresh_cursor();	
}

/* Funcion que mueve el cursor de la pantalla a la posicion indicada por "vga_curr_pos" */
void vga_refresh_cursor()
{
	uint_16 pos_cursor = SCREEN_RELATIVE_POSITION(vga_curr_pos);
	
	outb(VGA_CONTROLLER_CTRL_REGISTER, VGA_INSTRUCION_SET_HIGH_BYTE);	// Seteo el byte alto del cursor
	outb(VGA_CONTROLLER_DATA_REGISTER, pos_cursor >> 8);
	outb(VGA_CONTROLLER_CTRL_REGISTER, VGA_INSTRUCION_SET_LOW_BYTE);	// Seteo el byte bajo del cursor
	outb(VGA_CONTROLLER_DATA_REGISTER, pos_cursor);
}

/* Funcion que refresca el formato de todos los caracteres en pantalla */
void vga_refresh_screen()
{
	uint_8 formato = SCREEN_CONFIG_BYTE(vga_curr_char_color,vga_curr_scrn_color);	
	uint_32 posicion;
	for(posicion = SCREEN_ADDR_START; posicion < SCREEN_ADDR_END; posicion += 2) ((uint_8*) posicion)[1] = formato;
}

/* Funcion que limpia los caracteres de la linea en la que se encuentra el cursor */
void vga_clrln()
{
	vga_curr_pos = SCREEN_CURR_LINE_BEGIN_ADDR(vga_curr_pos);
	vga_refresh_cursor();
	
	uint_8 formato = SCREEN_CONFIG_BYTE(vga_curr_char_color, vga_curr_scrn_color);
	
	uint_32 posicion;
	for(posicion = vga_curr_pos ; posicion < vga_curr_pos + SCREEN_CANT_COLUMN * SCREEN_CHAR_SIZE ; posicion += 2)
	{
		((uint_8*) posicion)[0] = '\0';
		((uint_8*) posicion)[1] = formato;
	}
}

/* Funcion que limpia todos los caracteres de la pantalla */
void vga_clrscr()
{
	vga_curr_pos = SCREEN_ADDR_START;
	vga_refresh_cursor();
	
	uint_8 formato = SCREEN_CONFIG_BYTE(vga_curr_char_color, vga_curr_scrn_color);
	
	uint_32 posicion;
	for(posicion = vga_curr_pos ; posicion < SCREEN_ADDR_END ; posicion += 2)
	{
		((uint_8*) posicion)[0] = '\0';
		((uint_8*) posicion)[1] = formato;
	}
}

/* Funcion que imprime un caracter por pantalla */

void vga_putc(char chr)
{	
	switch(chr)
	{
		case('\n'):		// Enter
		{
			vga_curr_pos = SCREEN_NEXT_LINE_BEGIN_ADDR(vga_curr_pos);
			if(vga_curr_pos >= SCREEN_ADDR_END) vga_scroll_down();		// Si se fue de la pantalla hago un scroll			
			break;
		}
		
		case('\t'):		// Tab
		{
			vga_write("    ");			
			break;
		}
				
		default:
		{
			((uint_8*) vga_curr_pos)[0] = chr;		// Escribo el caracter
		
			uint_8 formato = SCREEN_CONFIG_BYTE(vga_curr_char_color, vga_curr_scrn_color);
							
			((uint_8*) vga_curr_pos)[1] = formato;	// Escribo el formato del caracter
			vga_curr_pos += 2;						// Avanzo a la siguiente posicion
			
			if(vga_curr_pos >= SCREEN_ADDR_END)		// Si me fui de rango, hago un scroll
				vga_scroll_down();	
						
			break;
		}
	}
}

/* Funcion que imprime un caracter por pantalla en la fila "row" y en la columna "column" */
void vga_putc_rc(uint_32 row, uint_32 column, char c)
{
	vga_curr_pos = SCREEN_ADDR_RC(row, column);
	vga_putc(c);
}

/* Funcion que imprime un string null-terminated */
uint_32 vga_write(char* string)
{	
	char* str = string;
	
	uint_32 cant = 0;
	
	char c;
	for(c = *str; c != '\0' ; c = *(++str), cant++)		// Imprimo hasta encontrar el caracter nulo
		vga_putc(c);
		
	return cant;
}

/* Funcion que imprime un string null-terminated en la fila "row" y en la columna "column" */
uint_32 vga_write_rc(uint_32 row, uint_32 column, char* string)
{
	vga_curr_pos = SCREEN_ADDR_RC(row, column);
	return vga_write(string);
}

uint_32 vga_write_uint32_hex(uint_32 n, uint_32 cant_digitos);
uint_32 vga_write_uint32_dec(uint_32 n);
uint_32 vga_write_sint32_dec(sint_32 n);

/* 
 * vga_printf - Imprime un string con formato
 * 
 */

uint_32 vga_printf(char* string, ... )
{	
	va_list vargs;
    va_start(vargs, string);
    
    uint_32 cant = 0;
	
	char c;
	while((c = *string++) != '\0')
	{
        if (c != '%')
        {
            vga_putc(c);
            cant++;
		}
        else
        {
			// Si encuentro el caracter de formato veo que imprimo
			
			char digits_strn[16] = {0};
			
			int i;
			for(i = 0; i < 16 && isdigit(c = *string++); digits_strn[i] = c, i++)	// Guardo los digitos
				;
				
			int digits = atoi(digits_strn);
			
			switch(c)
			{
				case('i'):
				case('d'):		// Tipo entero
				{					
					cant += vga_write_sint32_dec((sint_32) va_arg(vargs, int));					
					break;
				}
				
				case('u'):		// Tipo entero unsigned
				{
					cant += vga_write_uint32_dec((uint_32) va_arg(vargs, int));
					break;
				}

				case('h'):		// Tipo hexa
				{
					cant += vga_write_uint32_hex((uint_32) va_arg(vargs, int), (digits ? digits : 8));					
					break;
				}

				case('s'):		// String
				{
					cant += vga_write((char*) va_arg(vargs, char*));					
					break;
				}

				case('%'):		// %
				{
					vga_putc('%');				
					cant++;
					break;
				}

				default:		// Si no se reconocio el caracter de formato...
				{
					char err[] = "( )\n";
					err[1] = c;
					vga_write("vga_printf: tipo no reconocido ");
					vga_write(err);										
					break;
				}
			}
        }
    }

    va_end(vargs);	
	vga_refresh_cursor();
	
	return cant;
}

/* 
 * vga_printf - Imprime un string con formato en la fila @row y columna @column
 * 
 */

uint_32 vga_printf_rc(uint_32 row, uint_32 column, char* string, ... )
{
	vga_curr_pos = SCREEN_ADDR_RC(row, column);
	
	va_list vargs;
	va_start(vargs, string);
	
	uint_32 cant = vga_printf(string, vargs);
	
	va_end(vargs);
	
	return cant;
}

/* 
 * vga_write_uint32_hex - Imprime por pantalla el numero @n en base hexadecimal con @cant_digitos cifras
 * 
 * Si @cant_digitos no alcanzan para la representacion del numero, SE ROMPE TODOOO
 *
 */
 
uint_32 vga_write_uint32_hex(uint_32 n, uint_32 cant_digitos)
{
	char str[cant_digitos + 1];
	
	itoa(n, str, 16);
	
	uint_32 count = vga_write("0x");
	
	cant_digitos -= strlen(str);
	while(cant_digitos--)			// Completo con 0's lo que me falta
	{
		vga_putc('0');
		count++;
	}
	
	return count + vga_write(str);
}

/* Funcion que imprime un entero de 32 bits en decimal */
uint_32 vga_write_uint32_dec(uint_32 n)
{
	char str[16];
	
	itoa((unsigned int)n, str, 10);		

	return vga_write(str);
}

/* Funcion que imprime un entero de 32 bits en decimal */
uint_32 vga_write_sint32_dec(sint_32 n)
{
	char str[16];
	
	itoa(n, str, 10);

	return vga_write(str);
}

/* Funcion que realiza un scroll down de la pantalla */
void vga_scroll_down()
{
	memcpy((void*) SCREEN_ADDR_START, (void*) SCREEN_ADDR_START + (SCREEN_CANT_COLUMN * SCREEN_CHAR_SIZE), SCREEN_ADDR_END - SCREEN_ADDR_START);
	vga_curr_pos -= SCREEN_CANT_COLUMN * SCREEN_CHAR_SIZE;	// Subo una linea
	vga_clrln(vga_curr_pos);
}

void vga_set_char_color(uint_8 color)
{
	vga_curr_char_color = color;
}

void vga_set_scrn_color(uint_8 color)
{
	/* Si el color tiene el ultimo bit seteado, no lo permito como color de fondo */
	if(color & 0x8) return;
	vga_curr_scrn_color = color;
}
